package org.bouncycastle.jce.provider;

import java.security.spec.AlgorithmParameterSpec;

import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.PBEParameterSpec;

import org.bouncycastle.crypto.CipherParameters;
import org.bouncycastle.crypto.PBEParametersGenerator;
import org.bouncycastle.crypto.digests.MD5Digest;
import org.bouncycastle.crypto.digests.SHA1Digest;
import org.bouncycastle.crypto.digests.SHA256Digest;
import org.bouncycastle.crypto.generators.OpenSSLPBEParametersGenerator;
import org.bouncycastle.crypto.generators.PKCS12ParametersGenerator;
import org.bouncycastle.crypto.generators.PKCS5S1ParametersGenerator;
import org.bouncycastle.crypto.generators.PKCS5S2ParametersGenerator;
import org.bouncycastle.crypto.params.DESParameters;
import org.bouncycastle.crypto.params.KeyParameter;
import org.bouncycastle.crypto.params.ParametersWithIV;

public interface PBE {
    //
    // PBE Based encryption constants - by default we do PKCS12 with SHA-1
    //
    static final int MD5 = 0;
    static final int SHA1 = 1;
    static final int RIPEMD160 = 2;
    static final int TIGER = 3;
    static final int SHA256 = 4;
    static final int MD2 = 5;

    static final int PKCS5S1 = 0;
    static final int PKCS5S2 = 1;
    static final int PKCS12 = 2;
    static final int OPENSSL = 3;

    /**
     * uses the appropriate mixer to generate the key and IV if necessary.
     */
    static class Util {
        static private PBEParametersGenerator makePBEGenerator(int type,
                int hash) {
            PBEParametersGenerator generator;

            if (type == PKCS5S1) {
                switch (hash) {
                // BEGIN android-removed
                // case MD2:
                // generator = new PKCS5S1ParametersGenerator(new MD2Digest());
                // break;
                // END android-removed
                    case MD5:
                        generator = new PKCS5S1ParametersGenerator(
                                new MD5Digest());
                        break;
                    case SHA1:
                        generator = new PKCS5S1ParametersGenerator(
                                new SHA1Digest());
                        break;
                    default:
                        throw new IllegalStateException(
                                "PKCS5 scheme 1 only supports MD2, MD5 and SHA1.");
                }
            } else if (type == PKCS5S2) {
                generator = new PKCS5S2ParametersGenerator();
            } else if (type == PKCS12) {
                switch (hash) {
                // BEGIN android-removed
                // case MD2:
                // generator = new PKCS12ParametersGenerator(new MD2Digest());
                // break;
                // END android-removed
                    case MD5:
                        generator = new PKCS12ParametersGenerator(
                                new MD5Digest());
                        break;
                    case SHA1:
                        generator = new PKCS12ParametersGenerator(
                                new SHA1Digest());
                        break;
                    // BEGIN android-removed
                    // case RIPEMD160:
                    // generator = new PKCS12ParametersGenerator(new
                    // RIPEMD160Digest());
                    // break;
                    // case TIGER:
                    // generator = new PKCS12ParametersGenerator(new
                    // TigerDigest());
                    // break;
                    // END android-removed
                    case SHA256:
                        generator = new PKCS12ParametersGenerator(
                                new SHA256Digest());
                        break;
                    default:
                        throw new IllegalStateException(
                                "unknown digest scheme for PBE encryption.");
                }
            } else {
                generator = new OpenSSLPBEParametersGenerator();
            }

            return generator;
        }

        /**
         * construct a key and iv (if necessary) suitable for use with a Cipher.
         */
        static CipherParameters makePBEParameters(JCEPBEKey pbeKey,
                AlgorithmParameterSpec spec, String targetAlgorithm) {
            if ((spec == null) || !(spec instanceof PBEParameterSpec)) {
                throw new IllegalArgumentException(
                        "Need a PBEParameter spec with a PBE key.");
            }

            PBEParameterSpec pbeParam = (PBEParameterSpec) spec;
            PBEParametersGenerator generator = makePBEGenerator(
                    pbeKey.getType(), pbeKey.getDigest());
            byte[] key = pbeKey.getEncoded();
            CipherParameters param;

            if (pbeKey.shouldTryWrongPKCS12()) {
                key = new byte[2];
            }

            generator.init(key, pbeParam.getSalt(),
                    pbeParam.getIterationCount());

            if (pbeKey.getIvSize() != 0) {
                param = generator.generateDerivedParameters(
                        pbeKey.getKeySize(), pbeKey.getIvSize());
            } else {
                param = generator
                        .generateDerivedParameters(pbeKey.getKeySize());
            }

            if (targetAlgorithm.startsWith("DES")) {
                if (param instanceof ParametersWithIV) {
                    KeyParameter kParam = (KeyParameter) ((ParametersWithIV) param)
                            .getParameters();

                    DESParameters.setOddParity(kParam.getKey());
                } else {
                    KeyParameter kParam = (KeyParameter) param;

                    DESParameters.setOddParity(kParam.getKey());
                }
            }

            for (int i = 0; i != key.length; i++) {
                key[i] = 0;
            }

            return param;
        }

        /**
         * generate a PBE based key suitable for a MAC algorithm, the key size
         * is chosen according the MAC size, or the hashing algorithm, whichever
         * is greater.
         */
        static CipherParameters makePBEMacParameters(JCEPBEKey pbeKey,
                AlgorithmParameterSpec spec) {
            if ((spec == null) || !(spec instanceof PBEParameterSpec)) {
                throw new IllegalArgumentException(
                        "Need a PBEParameter spec with a PBE key.");
            }

            PBEParameterSpec pbeParam = (PBEParameterSpec) spec;
            PBEParametersGenerator generator = makePBEGenerator(
                    pbeKey.getType(), pbeKey.getDigest());
            byte[] key = pbeKey.getEncoded();
            CipherParameters param;

            if (pbeKey.shouldTryWrongPKCS12()) {
                key = new byte[2];
            }

            generator.init(key, pbeParam.getSalt(),
                    pbeParam.getIterationCount());

            param = generator.generateDerivedMacParameters(pbeKey.getKeySize());

            for (int i = 0; i != key.length; i++) {
                key[i] = 0;
            }

            return param;
        }

        /**
         * construct a key and iv (if necessary) suitable for use with a Cipher.
         */
        static CipherParameters makePBEParameters(PBEKeySpec keySpec, int type,
                int hash, int keySize, int ivSize) {
            PBEParametersGenerator generator = makePBEGenerator(type, hash);
            byte[] key;
            CipherParameters param;

            if (type == PKCS12) {
                key = PBEParametersGenerator.PKCS12PasswordToBytes(keySpec
                        .getPassword());
            } else {
                key = PBEParametersGenerator.PKCS5PasswordToBytes(keySpec
                        .getPassword());
            }

            generator.init(key, keySpec.getSalt(), keySpec.getIterationCount());

            if (ivSize != 0) {
                param = generator.generateDerivedParameters(keySize, ivSize);
            } else {
                param = generator.generateDerivedParameters(keySize);
            }

            for (int i = 0; i != key.length; i++) {
                key[i] = 0;
            }

            return param;
        }

        /**
         * generate a PBE based key suitable for a MAC algorithm, the key size
         * is chosen according the MAC size, or the hashing algorithm, whichever
         * is greater.
         */
        static CipherParameters makePBEMacParameters(PBEKeySpec keySpec,
                int type, int hash, int keySize) {
            PBEParametersGenerator generator = makePBEGenerator(type, hash);
            byte[] key;
            CipherParameters param;

            if (type == PKCS12) {
                key = PBEParametersGenerator.PKCS12PasswordToBytes(keySpec
                        .getPassword());
            } else {
                key = PBEParametersGenerator.PKCS5PasswordToBytes(keySpec
                        .getPassword());
            }

            generator.init(key, keySpec.getSalt(), keySpec.getIterationCount());

            param = generator.generateDerivedMacParameters(keySize);

            for (int i = 0; i != key.length; i++) {
                key[i] = 0;
            }

            return param;
        }
    }
}
